﻿using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using ShootSearch.Util;
using System.Collections;
using ShootSearch.Logger;

namespace ShootSearch.FileSystem
{
    /// <summary>
    /// 这个文件系统是在天网大文件系统的构思之上完成的一个小型大文件系统,
    /// 可以对二进制进行读写,但是缺点是无法删除文件.
    /// 还有一个问题就是不是实时可以写入和读取的.
    /// </summary>
    public class STFileSystem : IFileSystem
    {
        /// <summary>
        /// 常量:文件名stfile.main
        /// </summary>
        public const string FILENAME = "stfile.main";
        /// <summary>
        /// 常量:索引文件名stfile.index
        /// </summary>
        public const string INDEXNAME = "stfile.index";

        #region 私有变量
        /// <summary>
        /// 文件系统流
        /// </summary>
        private FileStream file;
        /// <summary>
        /// 索引文件流
        /// </summary>
        private FileStream index;

        private bool isClosed;
        /// <summary>
        /// 索引信息表
        /// </summary>
        private Hashtable hash;
        /// <summary>
        /// 编码
        /// </summary>
        private Encoding encode;
        /// <summary>
        /// IO物理路径
        /// </summary>
        private string ioPath;
        /// <summary>
        /// 线程锁,多线程安全操作
        /// </summary>
        private object locker = new object();
        /// <summary>
        /// 只写模式
        /// </summary>
        private bool writeonly;
        /// <summary>
        /// 是否使用MD5编码的Key
        /// </summary>
        private bool useMD5Key;
        /// <summary>
        /// 当前文件系统的标识符
        /// </summary>
        private string currentFSID = "default";
        /// <summary>
        /// 文件系统基本路径+标识符路径
        /// </summary>
        private string currentPath; 
        #endregion
         
        /// <summary>
        /// 是否使用MD5编码的Key
        /// </summary>
        public bool UseMD5Key 
        {
            get { return useMD5Key; }
            set { useMD5Key = value; }
        }

        /// <summary>
        /// 设置路径
        /// </summary>
        public string IOPath
        {
            get { return ioPath; }
            set
            {
                //格式化路径
                ioPath = IO.FormatDirName(value, true);
            }
        }

        /// <summary>
        /// 文件系统读取事件
        /// </summary>
        public event FileSystemActionEventHandler FileSystemRead;
        /// <summary>
        /// 文件系统写入事件
        /// </summary>
        public event FileSystemActionEventHandler FileSystemWrite;

        #region 构造方法
        /// <summary>
        /// 无参构造方法
        /// </summary>
        public STFileSystem()
        {
            encode = Encoding.UTF8;
            writeonly = false;
        }

        /// <summary>
        /// 带有IO路径的构造,自动调用CreateOpen方法
        /// </summary>
        /// <param name="iopath">IO路径</param>
        public STFileSystem(string iopath)
        {
            encode = Encoding.UTF8;
            CreateOpen(iopath);
            writeonly = false;
        } 
        #endregion

        #region 使用字符串进行编码设置
        /// <summary>
        /// 使用字符串进行编码设置
        /// </summary>
        /// <param name="codename"></param>
        public void SetEncode(string codename)
        {
            encode = Encoding.UTF8;
            try
            {
                encode = Encoding.GetEncoding(codename);
            }
            catch (Exception e)
            {
                Log.Warn("STFileSystem编码设置错误!" + e);
            }

        } 
        #endregion

        #region IFileSystem 成员

        #region IFileSystem属性
        /// <summary>
        /// 设置默认编码
        /// </summary>
        public Encoding Encode
        {
            get
            {
                return encode;
            }
            set
            {
                if (value != null)
                    encode = value;
            }
        }

        /// <summary>
        /// 只写模式,可以节约内存,当启用此模式时,
        /// ID标识符可能不唯一,由此可能带来一些错误!!!
        /// 导致ID一样的后进入的文件永久无法被读取!!!
        /// </summary>
        public bool WriteOnly
        {
            get { return writeonly; }
            set 
            {
                writeonly = value;
                if (writeonly)
                {
                    currentPath = IO.FormatDirName(this.ioPath + currentFSID + "\\", true);
                    if (index == null)
                        index = new FileStream(currentPath + INDEXNAME, FileMode.OpenOrCreate, FileAccess.ReadWrite);
                    //将指针移动到文件末尾
                    index.Seek(0, SeekOrigin.End);

                }
                else
                {
                    if (index != null)
                        index.Close();
                }
            
            }
        } 
        #endregion

        #region 创建或者打开一个文件
        /// <summary>
        /// 创建或者打开一个文件
        /// </summary>
        /// <param name="iopath"></param>
        /// <returns></returns>
        public bool CreateOpen(string iopath)
        {
            IOPath = iopath;
            if (file == null)
            {
                List<string> ids = ListAllFSIDs();
                if (ids.Count > 0) currentFSID = ids[0];
            }
            currentPath = IO.FormatDirName(this.ioPath + currentFSID + "\\", true);            
            try
            {
                //确定文件读写方式
                //创建或着读取文件
                file = new FileStream(currentPath + FILENAME, FileMode.OpenOrCreate, FileAccess.ReadWrite);
                //首先初始化
                hash = new Hashtable();

                //如果索引文件存在就读取
                if (File.Exists(currentPath + INDEXNAME) && !writeonly)
                {
                    StreamReader sr = new StreamReader(currentPath + INDEXNAME, encode);
                    string line = sr.ReadLine();
                    while (line != null)
                    {
                        int p = line.IndexOf(" ID:");
                        string value = line.Substring(0, p);
                        string key = line.Substring(p + 4);
                        if (!hash.Contains(key))
                        {
                            hash.Add(key, value);
                        }
                        line = sr.ReadLine();
                    }
                    sr.Close();
                }
                //只写模式
                if (writeonly)
                {
                    index = new FileStream(currentPath + INDEXNAME, FileMode.OpenOrCreate, FileAccess.ReadWrite);
                    //将指针移动到文件末尾
                    index.Seek(0, SeekOrigin.End);
                }
                isClosed = false;
                //创建或者打开成功
                return true;
            }
            catch (Exception e)
            {
                Log.Error("STFileSystem文件创建错误!错误信息" + e);
                //创建失败
                return false;
            }
        }
        #endregion


        #region 读取一个文件
        /// <summary>
        /// 读取二进制文件流
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        public byte[] Read(string id)
        {
            lock (locker)
            {
                if (useMD5Key)
                    id = Tools.HashEncrypt(id, HashEncryptType.MD5);
                //确定已经初始化
                if (hash == null || !hash.Contains(id) || 
                    writeonly)
                    return null;
                //读取文件位置信息
                string[] info = hash[id].ToString().Split(',');
                if (info == null) return null;
                try
                {
                    //查找位置
                    long start = int.Parse(info[1]);
                    long end = int.Parse(info[2]);
                    //定位
                    file.Position = start;
                    //准备缓冲区
                    byte[] buffer = new byte[(int)(end - start)];
                    //读取
                    file.Read(buffer, 0, buffer.Length);
                    //#########################
                    //文件系统读取事件
                    FileSystemEventArgs e = new FileSystemEventArgs(id);
                    if (FileSystemRead != null) FileSystemRead(this, e);
                    //#########################
                    return buffer;
                }
                catch (Exception e)
                {
                    Log.Error("STFileSystem文件读取错误!错误信息" + e);
                    //读取失败
                    return null;
                }
            }
        }

        /// <summary>
        /// 读取文本
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        public string ReadText(string id)
        {
            lock (locker)
            {
                byte[] buffer = Read(id);
                //读取失败
                if (buffer == null) return null;
                //读取成功
                Stream stream = new MemoryStream(buffer);
                TextReader txtreader = new StreamReader(stream);
                string result = txtreader.ReadToEnd();
                stream.Close();
                txtreader.Close();
                return result;
            }
        } 
        #endregion

        #region 写入一个文件
        /// <summary>
        /// 以流的方式写入文件
        /// </summary>
        /// <param name"id">文件的标识符可以是物理的路径,或者是一个Url</param>
        /// <param name="fileName">文件名</param>
        /// <param name="b">文件内容</param>
        /// <returns>true 写入成功 false 失败</returns>
        public bool Write(string id, string fileName, byte[] b)
        {
            lock (locker)
            {
                //如果相同标识符的文件已经存在就退出
                if (hash == null || (hash.Contains(id) && !writeonly))
                    return false;
                try
                {
                    //首先将指针移动到文件末尾
                    file.Seek(0, SeekOrigin.End);

                    //每个文件的起始信息
                    //首先是文件信息的开始位置
                    string content = file.Position + ",";
                    //准备文件信息
                    string info = "ID:" + fileName + "\r\nSize:"
                        + b.Length + "\r\nDate:" + DateTime.Now.ToString("yyyy-MM-dd hh:mm:ss");
                    byte[] bi = encode.GetBytes(info);
                    //写入文件信息
                    file.Write(bi, 0, bi.Length);
                    //记录文件信息结束位置
                    content += file.Position + ",";
                    //写入文件内容
                    file.Write(b, 0, b.Length);
                    //记录文件内容结束位置
                    content += file.Position;
                    if (writeonly)
                    {
                        //如果是只写模式同步记录索引
                        if (useMD5Key)
                            content += " ID:" + Tools.HashEncrypt(id, HashEncryptType.MD5) + "\r\n";
                        else
                            content += " ID:" + id + "\r\n";
                        byte[] buffer = encode.GetBytes(content);

                        index.Write(buffer, 0, buffer.Length);
                    }
                    else
                    {
                        //将文件位置信息信息放入索引
                        if (useMD5Key)
                            hash.Add(Tools.HashEncrypt(id, HashEncryptType.MD5), content);
                        else
                            hash.Add(id, content);

                    }
                    //文件系统写入事件
                    FileSystemEventArgs e = new FileSystemEventArgs(id);
                    if (FileSystemWrite != null) FileSystemWrite(this, e);


                    return true;
                }
                catch (Exception e)
                {
                    Log.Error("STFileSystem文件关闭错误!错误信息" + e);
                    return false;
                }
            }
        }

        /// <summary>
        /// 以流的方式写入文件
        /// </summary>
        /// <param name"id">文件的标识符可以是物理的路径,或者是一个Url,默认文件名为文件ID</param>
        /// <param name="b">文件内容</param>
        /// <returns>true 写入成功 false 失败</returns>
        public bool Write(string id, byte[] b)
        {
            return Write(id, id, b);
        }

        /// <summary>
        /// 写入文本文件
        /// </summary>
        /// <param name"id">文件的标识符可以是物理的路径,或者是一个Url,默认文件名为文件ID</param>
        /// <param name="fileName">文件名</param>
        /// <param name="text">文件内容</param>
        /// <returns>true 写入成功 false 失败</returns>
        public bool WriteText(string id, string fileName, string text)
        {
            return Write(id, fileName, encode.GetBytes(text));
        }

        /// <summary>
        /// 写入文本文件
        /// </summary>
        /// <param name"id">文件的标识符可以是物理的路径,或者是一个Url,默认文件名为文件ID</param>
        /// <param name="text">文件内容</param>
        /// <returns>true 写入成功 false 失败</returns>
        public bool WriteText(string id, string text)
        {
            return Write(id, id, encode.GetBytes(text));
        }



        #endregion

        #region 关闭
        /// <summary>
        /// 关闭
        /// </summary>
        public void Close()
        {
            lock (locker)
            {
                try
                {
                    if (file != null && !isClosed)
                    {
                        //将缓存内容写入
                        file.Flush();
                        //关闭
                        file.Close();
                    }
                    //非只写模式需要记录索引
                    if (!writeonly)
                    {
                        index = new FileStream(this.currentPath + INDEXNAME, FileMode.Create, FileAccess.Write);
                        IDictionaryEnumerator ide = hash.GetEnumerator();
                        while (ide.MoveNext())
                        {
                            byte[] buffer = encode.GetBytes(ide.Value + " ID:" + ide.Key + "\r\n");
                            index.Write(buffer, 0, buffer.Length);
                        }
                    }

                    if (index != null&& !isClosed)
                    {
                        index.Flush();
                        index.Close();
                    }
                    isClosed = true;

                }
                catch (Exception e)
                {
                    Log.Error("STFileSystem文件关闭错误!错误信息" + e);
                }
            }
        } 
        #endregion

        #region 删除[无法使用!!!]
        /// <summary>
        /// 删除[无法使用!!!]
        /// </summary>
        /// <param name="id"></param>
        public void Delete(string id)
        {
            //这个简单文件系统的缺陷就是无法删除文件
        } 
        #endregion

        #region 将缓存写入
        /// <summary>
        /// 将缓存写入
        /// </summary>
        public void Flush()
        {
            try
            {
                if (!isClosed)
                {
                    //将缓存内容写入
                    if (file != null)
                        file.Flush();
                    //如果索引文件流不为空也写入
                    if (index != null)
                        index.Flush();
                }
            }
            catch (Exception e)
            {
                Log.Warn("缓存写入错误!" + e.ToString());
            }
        } 
        #endregion

        /// <summary>
        /// 获取或者设置当前文件系统标识符
        /// </summary>
        public string FSID
        {
            get { return currentFSID; }
            set
            {
                if (currentFSID != value)
                {
                    currentFSID = value;
                    //切换只发生在第二次设置上
                    if (file != null)
                        SwitchFileSystemID();
                }
            }
        }


        /// <summary>
        /// 获取当前文件系统的大小
        /// </summary>
        public long Length
        {
            get 
            {
                lock (locker)
                {
                    return file.Length;
                }
            }
        }

        /// <summary>
        /// 列出当前文件系统的所有标识符
        /// </summary>
        /// <returns></returns>
        public List<string> ListAllFSIDs()
        {
            List<string> results = new List<string>();
            string[] dirs = Directory.GetDirectories(this.ioPath);
            foreach (string s in dirs)
            {
                if (File.Exists(s + "\\" + FILENAME)
                    && File.Exists(s + "\\" + INDEXNAME))
                {
                    results.Add((new DirectoryInfo(s).Name));
                }
            }
            return results;
        }

        /// <summary>
        /// 列出当前FSID下的所有文件名
        /// </summary>
        /// <returns></returns>
        public List<string> ListFileNames()
        {
            List<string> names = new List<string>();
            if (hash != null)
            {
                IDictionaryEnumerator enumerator = hash.GetEnumerator();
                while (enumerator.MoveNext())
                {
                    names.Add(enumerator.Key.ToString());
                }
            }
            return names;
            
        }

        /// <summary>
        /// 切换文件系统标识符
        /// </summary>
        private void SwitchFileSystemID()
        {
            lock (locker)
            {
                try
                {
                    Close();
                }
                catch (Exception ex)
                {
                    Log.Error(ex);
                }
            }
            CreateOpen(this.ioPath);
        }

        #endregion


        #region IDisposable Members

        public void Dispose()
        {
            Close();
        }

        #endregion

        #region IConfigable Members

        public ShootSearch.Core.SpiderManConfig Config { get; set; }

        #endregion
    }
}
